﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Threading;

namespace WcfContrib.Utils
{
    /// <summary>
    /// Represents a sychronized (thread-safe) dictionary.
    /// </summary>
    /// <typeparam name="TKey">The type of the key.</typeparam>
    /// <typeparam name="TValue">The type of the value.</typeparam>
    public class SynchronizedDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IDictionary
    {
        class DisposableAction : IDisposable
        {
            Action action;
            bool disposed;

            public DisposableAction(Action disposeAction)
            {
                this.action = disposeAction;
            }

            public void Dispose()
            {
                if (!disposed)
                {
                    if (action != null)
                    {
                        action();
                    }
                    
                    disposed = true;
                }
            }
        }

        [ThreadStatic]
        bool _nonSync;

        Dictionary<TKey, TValue> store;
        ReaderWriterLockSlim lockObject;

        /// <summary>
        /// Initializes a new instance of the <see cref="SynchronizedDictionary&lt;TKey, TValue&gt;"/> class.
        /// </summary>
        public SynchronizedDictionary()
        {
            store = new Dictionary<TKey, TValue>();
            lockObject = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
        }

        /// <summary>
        /// Acquires a lock for the dictionary.
        /// </summary>
        /// <param name="writeLock">if set to <c>true</c> a write lock is taken.</param>
        /// <returns>A disposable state which releases the lock when it is disposed.</returns>
        public IDisposable AcquireLock(bool writeLock)
        {
            if (!_nonSync)
            {
                if (writeLock)
                {
                    lockObject.EnterWriteLock();
                    return new DisposableAction(() => lockObject.ExitWriteLock()); ;
                }
                else
                {
                    lockObject.EnterReadLock();
                    return new DisposableAction(() => lockObject.ExitReadLock());
                }
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// Performs the action without acquiring locks.
        /// </summary>
        /// <param name="action">The action.</param>
        public void PerformNonSynchronized(Action<SynchronizedDictionary<TKey, TValue>> action)
        {
            Perform(action, true);
        }

        /// <summary>
        /// Performs the action with acquiring locks.
        /// </summary>
        /// <param name="action">The action.</param>
        public void PerformSynchronized(Action<SynchronizedDictionary<TKey, TValue>> action)
        {
            Perform(action, false);
        }

        void Perform(Action<SynchronizedDictionary<TKey, TValue>> action, bool nonSync)
        {
            bool syncOriginal = _nonSync;
            
            try
            {
                _nonSync = nonSync;
                action(this);
            }
            finally
            {
                _nonSync = syncOriginal;
            }
        }

        #region IDictionary<TKey,TValue> Members

        /// <summary>
        /// Adds an element with the provided key and value to the <see cref="T:System.Collections.Generic.IDictionary`2"/>.
        /// </summary>
        /// <param name="key">The object to use as the key of the element to add.</param>
        /// <param name="value">The object to use as the value of the element to add.</param>
        /// <exception cref="T:System.ArgumentNullException">
        /// 	<paramref name="key"/> is null.
        /// </exception>
        /// <exception cref="T:System.ArgumentException">
        /// An element with the same key already exists in the <see cref="T:System.Collections.Generic.IDictionary`2"/>.
        /// </exception>
        /// <exception cref="T:System.NotSupportedException">
        /// The <see cref="T:System.Collections.Generic.IDictionary`2"/> is read-only.
        /// </exception>
        public void Add(TKey key, TValue value)
        {
            using (AcquireLock(true))
            {
                ((IDictionary<TKey, TValue>)store).Add(key, value);
            }
        }

        /// <summary>
        /// Determines whether the <see cref="T:System.Collections.Generic.IDictionary`2"/> contains an element with the specified key.
        /// </summary>
        /// <param name="key">The key to locate in the <see cref="T:System.Collections.Generic.IDictionary`2"/>.</param>
        /// <returns>
        /// true if the <see cref="T:System.Collections.Generic.IDictionary`2"/> contains an element with the key; otherwise, false.
        /// </returns>
        /// <exception cref="T:System.ArgumentNullException">
        /// 	<paramref name="key"/> is null.
        /// </exception>
        public bool ContainsKey(TKey key)
        {
            using (AcquireLock(false))
            {
                return ((IDictionary<TKey, TValue>)store).ContainsKey(key);
            }
        }

        /// <summary>
        /// Gets an <see cref="T:System.Collections.Generic.ICollection`1"/> containing the keys of the <see cref="T:System.Collections.Generic.IDictionary`2"/>.
        /// </summary>
        /// <value></value>
        /// <returns>
        /// An <see cref="T:System.Collections.Generic.ICollection`1"/> containing the keys of the object that implements <see cref="T:System.Collections.Generic.IDictionary`2"/>.
        /// </returns>
        public ICollection<TKey> Keys
        {
            get
            {
                return ((IDictionary<TKey, TValue>)store).Keys;
            }
        }

        /// <summary>
        /// Removes the element with the specified key from the <see cref="T:System.Collections.Generic.IDictionary`2"/>.
        /// </summary>
        /// <param name="key">The key of the element to remove.</param>
        /// <returns>
        /// true if the element is successfully removed; otherwise, false.  This method also returns false if <paramref name="key"/> was not found in the original <see cref="T:System.Collections.Generic.IDictionary`2"/>.
        /// </returns>
        /// <exception cref="T:System.ArgumentNullException">
        /// 	<paramref name="key"/> is null.
        /// </exception>
        /// <exception cref="T:System.NotSupportedException">
        /// The <see cref="T:System.Collections.Generic.IDictionary`2"/> is read-only.
        /// </exception>
        public bool Remove(TKey key)
        {
            using (AcquireLock(true))
            {
                return ((IDictionary<TKey, TValue>)store).Remove(key);
            }
        }

        /// <summary>
        /// Gets the value associated with the specified key.
        /// </summary>
        /// <param name="key">The key whose value to get.</param>
        /// <param name="value">When this method returns, the value associated with the specified key, if the key is found; otherwise, the default value for the type of the <paramref name="value"/> parameter. This parameter is passed uninitialized.</param>
        /// <returns>
        /// true if the object that implements <see cref="T:System.Collections.Generic.IDictionary`2"/> contains an element with the specified key; otherwise, false.
        /// </returns>
        /// <exception cref="T:System.ArgumentNullException">
        /// 	<paramref name="key"/> is null.
        /// </exception>
        public bool TryGetValue(TKey key, out TValue value)
        {
            using (AcquireLock(false))
            {
                return ((IDictionary<TKey, TValue>)store).TryGetValue(key, out value);
            }
        }

        /// <summary>
        /// Gets an <see cref="T:System.Collections.Generic.ICollection`1"/> containing the values in the <see cref="T:System.Collections.Generic.IDictionary`2"/>.
        /// </summary>
        /// <value></value>
        /// <returns>
        /// An <see cref="T:System.Collections.Generic.ICollection`1"/> containing the values in the object that implements <see cref="T:System.Collections.Generic.IDictionary`2"/>.
        /// </returns>
        public ICollection<TValue> Values
        {
            get
            {
                return ((IDictionary<TKey, TValue>)store).Values;
            }
        }

        /// <summary>
        /// Gets or sets the value with the specified key.
        /// </summary>
        /// <value>The value</value>
        public TValue this[TKey key]
        {
            get
            {
                using (AcquireLock(false))
                {
                    return ((IDictionary<TKey, TValue>)store)[key];
                }
                
            }
            set
            {
                using (AcquireLock(true))
                {
                    ((IDictionary<TKey, TValue>)store)[key] = value;
                }
            }
        }

        #endregion

        #region ICollection<KeyValuePair<TKey,TValue>> Members

        void ICollection<KeyValuePair<TKey,TValue>>.Add(KeyValuePair<TKey, TValue> item)
        {
            using (AcquireLock(true))
            {
                ((ICollection<KeyValuePair<TKey, TValue>>)store).Add(item);
            }
        }

        /// <summary>
        /// Removes all items from the <see cref="T:System.Collections.Generic.ICollection`1"/>.
        /// </summary>
        /// <exception cref="T:System.NotSupportedException">
        /// The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.
        /// </exception>
        public void Clear()
        {
            using (AcquireLock(true))
            {
                ((ICollection<KeyValuePair<TKey, TValue>>)store).Clear();
            }
        }

        bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
        {
            using (AcquireLock(false))
            {
                return ((ICollection<KeyValuePair<TKey, TValue>>)store).Contains(item);
            }
        }

        void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
        {
            using (AcquireLock(true))
            {
                ((ICollection<KeyValuePair<TKey, TValue>>)store).CopyTo(array, arrayIndex);
            }
        }

        /// <summary>
        /// Gets the number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1"/>.
        /// </summary>
        /// <value></value>
        /// <returns>
        /// The number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1"/>.
        /// </returns>
        public int Count
        {
            get 
            {
                return ((ICollection<KeyValuePair<TKey, TValue>>)store).Count; 
            }
        }

        bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly
        {
            get
            { 
                return ((ICollection<KeyValuePair<TKey, TValue>>)store).IsReadOnly; 
            }
        }

        bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
        {
            using (AcquireLock(true))
            {
                return ((ICollection<KeyValuePair<TKey, TValue>>)store).Remove(item);
            }
        }

        #endregion

        #region IEnumerable<KeyValuePair<TKey,TValue>> Members

        /// <summary>
        /// Returns an enumerator that iterates through the collection.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
        /// </returns>
        public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
        {
            return ((IEnumerable<KeyValuePair<TKey, TValue>>)store).GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        IEnumerator IEnumerable.GetEnumerator()
        {
            return ((IEnumerable)store).GetEnumerator();
        }

        #endregion

        #region IDictionary Members

        bool IDictionary.IsReadOnly
        {
            get
            {
                return ((IDictionary)store).IsReadOnly;
            }
        }

        void IDictionary.Add(object key, object value)
        {
            using (AcquireLock(true))
            {
                ((IDictionary)store).Add(key, value);
            }
        }

        bool IDictionary.Contains(object key)
        {
            using (AcquireLock(false))
            {
                return ((IDictionary)store).Contains(key);
            }
        }

        IDictionaryEnumerator IDictionary.GetEnumerator()
        {
            return ((IDictionary)store).GetEnumerator();
        }

        bool IDictionary.IsFixedSize
        {
            get { return ((IDictionary)store).IsFixedSize; }
        }

        ICollection IDictionary.Keys
        {
            get { return ((IDictionary)store).Keys; }
        }

        void IDictionary.Remove(object key)
        {
            using (AcquireLock(true))
            {
                ((IDictionary)store).Remove(key);
            }
        }

        ICollection IDictionary.Values
        {
            get { return ((IDictionary)store).Values; }
        }

        object IDictionary.this[object key]
        {
            get
            {
                using (AcquireLock(false))
                {
                    return ((IDictionary)store)[key];
                }
            }
            set
            {
                using (AcquireLock(true))
                {
                    ((IDictionary)store)[key] = value;
                }
            }
        }

        #endregion

        #region ICollection Members

        void ICollection.CopyTo(Array array, int index)
        {
            using (AcquireLock(true))
            {
                ((ICollection)store).CopyTo(array, index);
            }
        }

        bool ICollection.IsSynchronized
        {
            get { return !_nonSync; }
        }

        object ICollection.SyncRoot
        {
            get { return ((ICollection)store).SyncRoot; }
        }

        #endregion
    }
}
